home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Applications / Publishing / ImagePortfolio / Source / PaletteMatrix.m < prev    next >
Text File  |  1994-04-01  |  20KB  |  788 lines

  1. // -------------------------------------------------------------------------------------
  2. // PaletteMatrix.m
  3. // Martin D. Flynn, NeXT Computer, Inc.
  4. // You may freely copy, distribute and reuse the code in this example.
  5. // NeXT disclaims any warranty of any kind, expressed or implied, as to its
  6. // fitness for any particular use.
  7. // -------------------------------------------------------------------------------------
  8.  
  9. #import "PaletteMatrix.h"
  10. #import <stdlib.h>
  11. #import <stdio.h>
  12. #import <string.h>
  13. #import <mach/mach.h>
  14. #import <objc/List.h>
  15. #import <appkit/View.h>
  16. #import <appkit/Text.h>
  17. #import <appkit/NXImage.h>
  18. #import <appkit/Pasteboard.h>
  19. #import <appkit/FontManager.h>
  20. #import <appkit/SavePanel.h>
  21. #import <appkit/nextstd.h>
  22. #import <dpsclient/wraps.h>
  23. #import <math.h>
  24. #import "ImagePortfolio.h"            // main app
  25. #import "Portfolio.h"                // Matrix delegate
  26. #import "PaletteCell.h"                // Image view cells
  27.  
  28. // -------------------------------------------------------------------------------------
  29. static id        paletteCellClass = (id)nil;
  30. static char        *cutListPaths   = (char*)nil;
  31. static int        sigPasteboard   = -1;
  32. static char        *imagePathType  = (char*)nil;
  33. static id        pasteFont       = (id)nil;
  34.  
  35. // -------------------------------------------------------------------------------------
  36. #define    prefVERSION    1.02
  37. #define CELLCLASS    [PaletteCell class]
  38. #define    newCELL        [protoCell copy]
  39. #define    VSPACER        2.0
  40. #define    freeLIST(L)    { if (L) { [[L freeObjects] free]; L = (id)nil; } }
  41.  
  42. // -------------------------------------------------------------------------------------
  43. @implementation PaletteMatrix
  44.  
  45. // -------------------------------------------------------------------------------------
  46.  
  47. /* new instance */
  48. + newFrame:(const NXRect *)fRect
  49. {
  50.     return [[self allocFromZone:NXDefaultMallocZone()] initFrame:fRect];
  51. }
  52.  
  53. /* initialize instance */
  54. - initFrame:(const NXRect*)fRect
  55. {
  56.  
  57.     /* init globals */
  58.     if (!imagePathType) imagePathType = NXCopyStringBuffer("imagePathType");
  59.  
  60.     /* init super */
  61.     [super initFrame:fRect];
  62.     if (!paletteCellClass) paletteCellClass = CELLCLASS;
  63.  
  64.     /* init vars */
  65.     delegate = self;
  66.     pbCopyTiff = YES;
  67.     pbCopyPath = NO;
  68.     loadingCells = NO;
  69.     loadMutex = mutex_alloc();
  70.  
  71.     /* image list container */
  72.     imageList = [[[List alloc] initCount:1] empty];
  73.     deleteList = (id)nil;
  74.  
  75.     /* default protocell */
  76.     protoCell = [paletteCellClass new];
  77.     intercell.width  = VSPACER;
  78.     intercell.height = VSPACER;
  79.   
  80.     /* size */
  81.     [self setMode:NX_LISTMODE];
  82.     [self setEmptySelectionEnabled:YES];
  83.     [self setAutoscroll:YES];
  84.     [self setBackgroundGray:NX_LTGRAY];
  85.     [self setCellBackgroundGray:NX_LTGRAY];
  86.     [self setFlipped:YES];
  87.  
  88.     return self;
  89. }
  90.  
  91. /* free object */
  92. - free
  93. {
  94.     [imageList free];        // cells are freed by super
  95.     freeLIST(deleteList);
  96.     mutex_free(loadMutex);
  97.     return [super free];
  98. }
  99.  
  100. /* free all cells */
  101. - freeCells
  102. {
  103.     [self clearSelectedCell];
  104.     [self renewRows:0 cols:0];
  105.     [cellList freeObjects];
  106.     [imageList empty];
  107.     [self sizeToCells];
  108.     freeLIST(deleteList);
  109.     [window setDocEdited:YES];
  110.     return self;
  111. }
  112.  
  113. /* remove selected cells (return deleted list) */
  114. - freeSelectedCells
  115. {
  116.     int    i;
  117.     id    iCell, dList = [self selectedCellList];
  118.   
  119.     /* return if no selected cells */
  120.     if (!dList) return (id)nil;
  121.   
  122.     /* delete selected cells from matrix */
  123.     i = [dList count];
  124.     [self clearSelectedCell];
  125.     [self renewRows:0 cols:0];
  126.     mutex_lock(loadMutex);
  127.     while(i) { 
  128.         iCell = [dList objectAt:--i];
  129.         [imageList removeObject:iCell];
  130.         [cellList removeObject:iCell];
  131.         [iCell setDelegate:(id)nil];
  132.     }
  133.     mutex_unlock(loadMutex);
  134.     [self sizeToCells];
  135.     [window setDocEdited:YES];
  136.   
  137.     return dList;
  138. }
  139.   
  140. // -------------------------------------------------------------------------------------
  141. // set attributes
  142.   
  143. /* set delegate */
  144. - setDelegate:anObject
  145. {
  146.     delegate = anObject;
  147.     return self;
  148. }
  149.  
  150. /* set to font */
  151. - setFont:fontObj
  152. {
  153.     int i = [self cellCount];
  154.     [super setFont:fontObj];
  155.     [window disableDisplay];
  156.     while(i) [[cellList objectAt:--i] setFont:fontObj];
  157.     [protoCell setFont:fontObj];
  158.     conFlags.calcSize = YES;
  159.     [window reenableDisplay];
  160.     return self;
  161. }
  162.  
  163. - changeFont:sender
  164. {
  165.     [self setFont:[[FontManager new] convertFont:[self font]]];
  166.     return self;
  167. }
  168.  
  169. /* set preferences (THIS ASSUMES PROPER PREFERENCE BUFFER FORMAT) */
  170. - (BOOL)setPreferences:(char*)prefBuff returnRows:(int*)rows cols:(int*)cols
  171. {
  172.     NXSize    size;
  173.     float    ver, pointSize;
  174.     char    fontName[256];
  175.     id        o;
  176.     if (!prefBuff || !*prefBuff) return NO;
  177.     sscanf(prefBuff, "%f %f %f %d %d %s %f", &ver, 
  178.         &size.width, &size.height, cols, rows, fontName, &pointSize);
  179.     [self setCellSize:&size];
  180.     if (o = [Font newFont:fontName size:pointSize]) [self setFont:o];
  181.     return YES;
  182. }
  183.   
  184. // -------------------------------------------------------------------------------------
  185. // pasteboard
  186.  
  187. - (char*)imagePaths:list
  188. {
  189.     int        i, c, len = 0;
  190.     char    *fullPath, *p;
  191.     if (!(c = [list count])) return (char*)nil;
  192.     for (i=0;i<c;i++) { len += strlen([[list objectAt:i] imagePath]) + 1; }
  193.     fullPath = p = (char*)malloc(len + 1);
  194.     for (i=0;i<c;i++) { sprintf(p, "%s\t", [[list objectAt:i] imagePath]); p += strlen(p); }
  195.     *(p - 1) = 0;        // remove trailing tab (p > fullPath is guaranteed)
  196.     return fullPath;
  197. }
  198.  
  199. /* copy listed cells to pasteboard */
  200. - copyToPasteboard:list paths:(char*)listPaths
  201. {
  202.     char        *dt[2], *data, *path;
  203.     int            len, max, n = 0;
  204.     NXStream    *stream;
  205.     id            first, image, pb;
  206.   
  207.     /* return if no items to copy */
  208.     if (![list count]) return self;
  209.   
  210.     /* return if no selected cell */
  211.     pb = [Pasteboard newName:NXGeneralPboard];
  212.     if (!pb) { NXLogError("ImagePortfolio: no pasteboard"); return self; }
  213.   
  214.     /* declare pasteboard types */
  215.     if (pbCopyPath) dt[n++] = (char*)NXAsciiPboardType;
  216.     if (pbCopyTiff) dt[n++] = (char*)NXTIFFPboardType;
  217.     dt[n++] = (char*)imagePathType;
  218.     [pb declareTypes:dt num:n owner:NXApp];
  219.     sigPasteboard = [pb changeCount];
  220.   
  221.     /* get first image */
  222.     first = [list objectAt:0];
  223.   
  224.     /* write image representation to pasteboard */
  225.     if (pbCopyTiff) {
  226.         stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  227.         image = [first image];
  228.         [image setName:[first cellTitle]];
  229.         [image lockFocus];
  230.         [image writeTIFF:stream];
  231.         [image unlockFocus];
  232.         NXGetMemoryBuffer(stream, &data, &len, &max);
  233.         [pb writeType:NXTIFFPboardType data:data length:len];
  234.         NXClose(stream);
  235.         NXFreeObjectBuffer(data, len);
  236.     }
  237.   
  238.     /* write image path name to pasteboard */
  239.     if (pbCopyPath && (path = (char*)[first imagePath])) {
  240.         [pb writeType:NXAsciiPboardType data:path length:strlen(path) + 1];
  241.     }
  242.  
  243.     /* write custom type */
  244.     if (listPaths) [pb writeType:imagePathType data:listPaths length:strlen(listPaths) + 1];
  245.  
  246.     return self;
  247. }
  248.  
  249. /* copy TIFF from pasteboard */
  250. - copyFromPasteboard
  251. {
  252.     id            pb, pSave, imageId;
  253.     char        **types, *data, titleBuff[256], *tiffName = (char*)nil;
  254.     char        *path = (char*)[NXApp lastPath];
  255.     int            len;
  256.     NXStream    *stream;
  257.  
  258.     /* return if no pasteboard */
  259.     if (!(pb = [Pasteboard newName:NXGeneralPboard])) return (id)nil;
  260.  
  261.     /* check for custom type */
  262.     for (types = (char**)[pb types]; *types && strcmp(*types,imagePathType); types++);
  263.     if (*types) {
  264.         if (![pb readType:imagePathType data:&data length:&len]) return (id)nil;
  265.         [delegate loadFileList:data :NO:NO];
  266.         vm_deallocate(task_self(), (vm_address_t)data, (vm_size_t)len);
  267.         return (id)nil;
  268.     }
  269.   
  270.     /* search pasteboard types for a TIFF file */
  271.     for (types=(char**)[pb types]; *types && strcmp(*types,(char*)NXTIFFPboardType); types++);
  272.     if (!*types) return (id)nil;
  273.   
  274.     /* read data from pasteboard */
  275.     if (![pb readType:NXTIFFPboardType data:&data length:&len]) return (id)nil;
  276.   
  277.     /* single-pass loop */
  278.     for (;;) {
  279.   
  280.         /* load TIFF image */
  281.         stream = NXOpenMemory(data, len, NX_READONLY);
  282.         if (imageId = [NXImage alloc]) {
  283.             NXSize size;
  284.             [imageId setDataRetained:YES];
  285.             [imageId initFromStream:stream];
  286.             [imageId getSize:&size];
  287.             if (!size.width || !size.height) { [imageId free]; imageId = (id)nil; }
  288.         }
  289.         if (!imageId) { NXCloseMemory(stream, NX_FREEBUFFER); break; }
  290.     
  291.         /* display image */
  292.         sprintf(titleBuff, "%s - TIFF", ([imageId name]?[imageId name]:"NXImage"));
  293.         [delegate showImage:imageId title:titleBuff];
  294.  
  295.         /* display save panel */
  296.         pSave = [SavePanel new];
  297.         [pSave setTitle:"Save TIFF file image"];
  298.         [pSave setPrompt:"File:"];
  299.         [pSave setRequiredFileType:"tiff"];
  300.         [pSave setDirectory:path];
  301.         if ([pSave runModalForDirectory:path file:""]) tiffName = (char*)[pSave filename];
  302.   
  303.         /* save to file */
  304.         if (tiffName) NXSaveToFile(stream, tiffName);
  305.         NXCloseMemory(stream, NX_FREEBUFFER);
  306.         if (tiffName) [delegate loadFileList:tiffName :NO:NO];
  307.   
  308.         /* end single-pass loop */
  309.         break;
  310.     
  311.     }
  312.   
  313.     /* free memory */
  314.     vm_deallocate(task_self(), (vm_address_t)data, (vm_size_t)len);
  315.     [delegate showImage:(id)nil title:""];  
  316.     if (imageId) [imageId free];
  317.  
  318.     return self;
  319. }
  320.   
  321. // -------------------------------------------------------------------------------------
  322. // first responder
  323. #define    delKEY        0x7F
  324.  
  325. /* never relinquish firstResponder */
  326. - resignFirstResponder { return (id)nil; }
  327.  
  328. /* show preferences (menu connection should point here to allow disabling when necessary) */
  329. - showPreferences:sender { return [NXApp showPreferences:self]; }
  330.  
  331. /* perform close window functions */
  332. - performClose:sender { return [window performClose:sender]; }
  333. - performMiniaturize:sender { return [window performMiniaturize:sender]; }
  334.  
  335. /* accept keydown event (check for delete key) */
  336. - keyDown:(NXEvent*)e
  337. {
  338.     if (e->data.key.charCode == delKEY) return [self delete:(id)nil];
  339.     return [super keyDown:e];
  340. }
  341.  
  342. /* delete selected cells */
  343. - delete:sender
  344. {
  345.     freeLIST(deleteList);
  346.     deleteList = [self freeSelectedCells];
  347.     [self display];
  348.     return self;
  349. }
  350.  
  351. /* copy previously deleted cells back to imageList */
  352. - undelete:sender
  353. {
  354.     int    i, c;
  355.     id    iCell;
  356.     if (!deleteList) return self;
  357.     for (c = [deleteList count], i = 0; i < c; i++) {
  358.         iCell = [deleteList objectAt:i];
  359.         if ([self findCellWithImageFilePath:(char*)[iCell imagePath]]) continue;
  360.         [iCell setDelegate:delegate];
  361.         [self addImageCell:iCell];
  362.         [window setDocEdited:YES];
  363.     }
  364.     [deleteList free];
  365.     deleteList = (id)nil;
  366.     [self resizeAndDisplay];
  367.     return self;
  368. }
  369.  
  370. /* return true if undeletable */
  371. - (BOOL)undeletable
  372. {
  373.     return ([deleteList count])? YES : NO;
  374. }
  375.  
  376. /* cut selected cells */
  377. - cut:sender
  378. {
  379.     [self delete:sender];
  380.     if (!deleteList) return self;
  381.     if (cutListPaths) { free(cutListPaths); cutListPaths = (char*)nil; }
  382.     cutListPaths = [self imagePaths:deleteList];
  383.     [self copyToPasteboard:deleteList paths:cutListPaths];
  384.     return self;
  385. }
  386.  
  387. /* make a copy of the selected cells */
  388. - copy:sender
  389. {
  390.     id    dList = [self selectedCellList];
  391.     if (!dList) return self;
  392.     if (cutListPaths) { free(cutListPaths); cutListPaths = (char*)nil; }
  393.     cutListPaths = [self imagePaths:dList];
  394.     [self copyToPasteboard:dList paths:cutListPaths];
  395.     [dList free];
  396.     return self;
  397. }
  398.  
  399. /* copy cut cells back to imageList */
  400. - paste:sender
  401. {
  402.     if ([[Pasteboard new] changeCount] != sigPasteboard) [self copyFromPasteboard];
  403.     else [delegate loadFileList:cutListPaths :NO:NO];
  404.     [self resizeAndDisplay];
  405.     return self;
  406. }
  407.  
  408. /* select all cells */
  409. - selectAll:sender
  410. {
  411.     int    i;
  412.     if (mFlags.radioMode) return self;
  413.     i = [self imageListCount];
  414.     while(i) [[self imageAt:--i] setState:1];
  415.     [self display];
  416.     return self;
  417. }
  418.  
  419. /* sort images by name */
  420. - sortByCellTitle:sender
  421. {
  422.     [self shellSort:@selector(compareCellTitle::)];
  423.     [self resizeAndDisplay];
  424.     return self;
  425. }
  426.  
  427. /* copy font */
  428. - copyFont:sender
  429. {
  430.     pasteFont = [self font];
  431.     return self;
  432. }
  433.  
  434. /* paste font */
  435. - pasteFont:sender
  436. {
  437.     if (pasteFont) [self setFont:pasteFont];
  438.     return self;
  439. }
  440.  
  441. // -------------------------------------------------------------------------------------
  442. // first responder: save 
  443.  
  444. - (char*)getPreferenceString:(char*)buff
  445. {
  446.     int    rows, cols;
  447.     [delegate getDisplayedRows:&rows cols:&cols];
  448.     sprintf(buff, "%.2f %.0f %.0f %d %d %s %.0f", prefVERSION, 
  449.         cellSize.width, cellSize.height, cols, rows, [font name], [font pointSize]);
  450.     return buff;
  451. }
  452.  
  453. /* save file names */
  454. - save:sender   { return ([self imageListCount])? [delegate save:sender] : self; }
  455. - saveAs:sender { return ([self imageListCount])? [delegate saveAs:sender] : self; }
  456.  
  457. /* save to file (called by delegate) */
  458. - saveToFile:(char*)fileName
  459. {
  460.     int        i, c = [self imageListCount];
  461.     char    buff[512];
  462.     FILE    *fNum;
  463.   
  464.     /* get file name to save */
  465.     if (!fileName || !c) return (id)nil;
  466.   
  467.     /* open file */
  468.     fNum = fopen(fileName, "w");
  469.     if (!fNum) return (id)nil;
  470.  
  471.     /* write preferences and path names */
  472.     fprintf(fNum, ": %s\n", [self getPreferenceString:buff]); 
  473.     for (i = 0; i < c; i++) fprintf(fNum, "%s\n", [[self imageAt:i] imagePath]);
  474.   
  475.     /* close file and indicate that window has been saved */
  476.     fclose(fNum);
  477.     [window setDocEdited:NO];
  478.  
  479.     return self;
  480. }
  481.  
  482. // -------------------------------------------------------------------------------------
  483. // size
  484.  
  485. /* set intercell size */
  486. - setIntercell:(const NXSize*)aSize
  487. {
  488.     [super setIntercell:aSize];
  489.     [self sizeToCells];
  490.     return self;
  491. }
  492.   
  493. /* set image size */
  494. - setCellSize:(const NXSize*)size
  495. {
  496.     int    i = [self cellCount];
  497.  
  498.     /* update cell size */
  499.     [super setCellSize:size];
  500.   
  501.     /* notify cells of size change */
  502.     while(i) [[cellList objectAt:--i] setCellSize:size];
  503.     [protoCell setCellSize:size];
  504.   
  505.     /* recalc sizes */
  506.     [self sizeToCells];
  507.   
  508.     return self;
  509. }
  510.  
  511. /* readjust matrix to fit cells */
  512. - sizeToCells
  513. {
  514.     int        i, rows, cols, cnt;
  515.     id        oldCell;
  516.  
  517.     /* clear selected cells */
  518.     [self clearSelectedCell];
  519.   
  520.     /* adjust rows/cols */
  521.     cnt = [self imageListCount];
  522.     cols = (int)((frame.size.width + intercell.width) / (cellSize.width + intercell.width));
  523.     if (cols < 1) cols = 1;
  524.     rows = (cnt + cols - 1) / cols;
  525.     if (rows < 1) rows = 1;
  526.     [self renewRows:rows cols:cols];
  527.   
  528.     /* replace cells (and free old cells) */
  529.     for (i = 0; i < cnt; i++) {
  530.         oldCell = [cellList replaceObjectAt:i with:[self imageAt:i]];
  531.         if (oldCell && ![oldCell image]) [oldCell free];
  532.     }
  533.   
  534.     /* now resize matrix */
  535.     [super sizeToCells];
  536.   
  537.     return self;
  538. }
  539.  
  540. /* resize matrix and redisplay */
  541. - resizeAndDisplay
  542. {
  543.     [self sizeToCells];
  544.     [self display];
  545.     return self;
  546. }
  547.  
  548. /* return cellSize */
  549. - (NXSize*)cellSize
  550. {
  551.     return &cellSize;
  552. }
  553.  
  554. /* return intercellSize */
  555. - (NXSize*)intercell
  556. {
  557.     return &intercell;
  558. }
  559.  
  560. // -------------------------------------------------------------------------------------
  561. // add cell methods
  562.  
  563. /* called by main thread when cell was added */
  564. - _updateCells
  565. {
  566.     [self resizeAndDisplay];
  567.     loadingCells = NO;
  568.     return self;
  569. }
  570.  
  571. /* add new image */
  572. - addImage:(const char*)filePath
  573. {
  574.     id    iCell;
  575.   
  576.     /* see if its already in the list */
  577.     if ([self findCellWithImageFilePath:(char*)filePath]) return (id)nil;
  578.   
  579.     /* add and show cell */
  580.     iCell = newCELL;
  581.     if (![iCell setImageFile:filePath]) { [iCell free]; return (id)nil; }
  582.     [iCell setDelegate:delegate];
  583.     [self addImageCell:iCell];
  584.     if (!loadingCells) {
  585.         loadingCells = YES;
  586.         [self mainThreadPerform:@selector(_updateCells) wait:NO];
  587.     }
  588.   
  589.     return iCell;
  590. }
  591.  
  592. /* return current number of image cells */
  593. - (int)imageListCount
  594. {
  595.     int    cnt;
  596.     mutex_lock(loadMutex);
  597.     cnt = [imageList count];
  598.     mutex_unlock(loadMutex);
  599.     return cnt;
  600. }
  601.  
  602. /* return indexed image cell */
  603. - imageAt:(int)index
  604. {
  605.     id    iCell;
  606.     mutex_lock(loadMutex);
  607.     iCell = [imageList objectAt:index];
  608.     mutex_unlock(loadMutex);
  609.     return iCell;
  610. }
  611.  
  612. /* add image cell */
  613. - addImageCell:iCell
  614. {
  615.     mutex_lock(loadMutex);
  616.     [imageList addObject:iCell];
  617.     mutex_unlock(loadMutex);
  618.     return iCell;
  619. }
  620.  
  621. // -------------------------------------------------------------------------------------
  622. // cell selection
  623.  
  624. /* return cell at spcified mouse location */
  625. - getCellAtLocation:(NXPoint*)mousePt
  626. {
  627.     float    x, y;
  628.     float     matWidth  = (numCols * (cellSize.width  + intercell.width )) - intercell.width ;
  629.     float     matHeight = (numRows * (cellSize.height + intercell.height)) - intercell.height;
  630.     int        i, r = -1, c = -1;
  631.     NXPoint    pt = *mousePt;
  632.     
  633.     /* convert location to matrix */
  634.     [self convertPoint:&pt fromView:(id)nil];
  635.     
  636.     /* check location is within bounds */
  637.     if ((pt.x < bounds.origin.x) || (pt.x > bounds.origin.x + matWidth ) ||
  638.         (pt.y < bounds.origin.y) || (pt.y > bounds.origin.y + matHeight)   ) return (id)nil;
  639.     
  640.     /* find column */
  641.     for (x=bounds.origin.x, i=0; (i<numCols) && (pt.x>=x); i++, x+=intercell.width) {
  642.         x += cellSize.width;
  643.         if (pt.x < x) { c = i; break; }
  644.     }
  645.     if (c < 0) return (id)nil;
  646.     
  647.     /* find row */
  648.     for (y=bounds.origin.y, i=0; (i<numRows) && (pt.y>=y); i++, y+=intercell.height) {
  649.         y += cellSize.height;
  650.         if (pt.y < y) { r = i; break; }
  651.     }
  652.     if (r < 0) return (id)nil;
  653.  
  654.     /* return cell at location */
  655.     return [self cellAt:r :c];
  656.     
  657. }
  658.  
  659. /* intercept mouse down event */
  660. - mouseDown:(NXEvent*)e
  661. {
  662.  
  663.     /* ignore while images are being loaded into palette */
  664.     if ([delegate isLoading]) return self;
  665.     
  666.     /* check for Alternate-Shift pressed */
  667.     if ((e->flags) & NX_ALTERNATEMASK) {
  668.         NXPoint pt = e->location;
  669.         id cellId = [self getCellAtLocation:&pt];
  670.         NXRect rect = { { 0.0, 0.0 }, {48.0, 48.0 } };
  671.         if (!cellId || ![cellId imagePath]) return self;
  672.         [self convertPoint:&pt fromView:(id)nil];
  673.         rect.origin.x = pt.x - rect.size.width  / 2.0;
  674.         rect.origin.y = pt.y - rect.size.height / 2.0;
  675.         [delegate _unregisterWindow];
  676.         [self dragFile:[cellId imagePath] fromRect:&rect slideBack:YES event:e];
  677.         [delegate _registerWindow];
  678.         return self;
  679.     }
  680.     
  681.     /* return regular mouse-down event */
  682.     return [super mouseDown:e];
  683.     
  684. }
  685.  
  686. /* return true if there are any selectable cells */
  687. - (BOOL)anySelectableCells
  688. {
  689.     return [self imageListCount]? YES : NO;
  690. }
  691.  
  692. /* find image with name */
  693. - findCellWithImageFilePath:(char*)p
  694. {
  695.     int    i;
  696.     id    o, f = (id)nil;
  697.     i = [self imageListCount];
  698.     while(i) if (!strcmp([(o=[self imageAt:--i]) imagePath], p)) { f = o; break; }
  699.     return f;
  700. }
  701.  
  702. /* return list of selected cells */
  703. - (int)selectedCellCount
  704. {
  705.     int    c = 0, i;
  706.     i = [self imageListCount];
  707.     while(i) if ([[self imageAt:--i] isSelected]) c++;
  708.     return c;
  709. }
  710.  
  711. /* return first selected cell found */
  712. - selectedCell
  713. {
  714.     int    i;
  715.     id    c, r = (id)nil;
  716.     i = [self imageListCount];
  717.     while(i) if ([(c = [self imageAt:--i]) isSelected]) r = c;
  718.     return r;
  719. }
  720.   
  721. /* return list of selected cells */
  722. - selectedCellList
  723. {
  724.     int    i;
  725.     id    c, list = listALLOC(2);
  726.     i = [self imageListCount];
  727.     while(i) if ([(c=[self imageAt:--i]) isSelected]) [list addObject:c];
  728.     if (![list count]) { [list free]; list = (id)nil; }
  729.     return list;
  730. }
  731.  
  732. /* return string containing file names of all selected cells */
  733. - (char*)selectedCellPaths
  734. {
  735.     id        list = [self selectedCellList];
  736.     char    *fullPath = [self imagePaths:list];
  737.     [list free];
  738.     return fullPath;
  739. }
  740.  
  741. // -------------------------------------------------------------------------------------
  742. // sort compare methods
  743.  
  744. /* sort/compare by image name */
  745. - (int)compareCellTitle:image1:image2
  746. {
  747.     return strcasecmp([image1 cellTitle], [image2 cellTitle]);
  748. }
  749.  
  750. // -------------------------------------------------------------------------------------
  751. // sort images
  752.  
  753. /* indexed from 1 */
  754. #define    _COMPARE(I, J)    ((int)[self perform:sortCompare with:(I) with:(J)])
  755. #define    _ELEM(i)        [imageList objectAt:(i) - 1]
  756. #define    _setELEM(i, V)    [imageList replaceObjectAt:(i) - 1 with:(V)]
  757.  
  758. /* shell sort: O(N ^ 1.5) */
  759. - shellSort:(SEL)sortCompare
  760. {
  761.     int    i, j, h, N;
  762.     id    elem, temp;
  763.   
  764.     /* check for invalid compare method */
  765.     if (!sortCompare) return (id)nil;
  766.  
  767.     /* lock imageList */
  768.     mutex_lock(loadMutex);
  769.  
  770.       /* determine h: (1 + 3 + 3^2 + 3^3 + 3^4 + ...) [ h = pow(3,floor(log(2*N+1)/log(3))) ]*/
  771.     for (N = [imageList count], h = 1; h <= N; h = 3 * h + 1);
  772.   
  773.     /* shell sort */
  774.     while (h > 1)
  775.     for (h /= 3, i = h + 1; i <= N; i++) {
  776.         elem = _ELEM(i);
  777.         for (j=i; (j>h) && (_COMPARE((temp=_ELEM(j-h)),elem)>0); j-=h) _setELEM(j, temp);
  778.         _setELEM(j, elem);
  779.     }
  780.  
  781.     /* unlock imageList */
  782.     mutex_unlock(loadMutex);
  783.  
  784.     return self;
  785. }
  786.  
  787. @end
  788.